#ifndef PICCOLO_MATERIAL_H
#define PICCOLO_MATERIAL_H

#include "PiccoloTexture.h"
#include "PiccoloSampler.h"
#include "Graphics/PiccoloGraphicsContext.h"

#include "entt/core/type_info.hpp"

namespace Piccolo
{
    // -----------------------------------------Shader Params----------------------------------------
    struct TextureParam
    {
        bool enable;
        alignas(4) float uvRotation { 0.f };
        alignas(16) glm::vec4 uvTransform { 1.f, 1.f, 0.f, 0.f };       // x, y -> scale, z, w -> translation
    };

    struct ModelPushConstant
    {
        alignas(16) glm::mat4 modelMatrix;
        alignas(16) glm::mat4 normalMatrix;
    };

    // ---------------------------------------------------------------------------------

    struct TextureView
    {
        PiccoloTexture* texture = nullptr;
        PiccoloSampler* sampler = nullptr;
        bool bEnable = true;
        glm::vec2 uvTranslation{ 0.f, 0.f };
        float uvRotation{ 0.f };
        glm::vec2 uvScale{ 1.f, 1.f };

        bool IsValid() const
        {
            return bEnable && texture != nullptr && sampler != nullptr;
        }
    };

    class PMaterial
    {
    public:
        PMaterial(const PMaterial&) = delete;
        PMaterial& operator=(const PMaterial&) = delete;

        static void UpdateTextureParams(const TextureView* textureView, TextureParam* textureParam)
        {
            textureParam->enable = textureView->IsValid() && textureView->bEnable;
            textureParam->uvRotation = textureView->uvRotation;
            textureParam->uvTransform = { textureView->uvScale.x, textureView->uvScale.y, textureView->uvTranslation.x, textureView->uvTranslation.y };
        }

        int32_t GetIndex() const { return mIndex; }
        bool ShouldFlushParams() const { return bShouldFlushParams; }
        bool ShouldFlushResource() const { return bShouldFlushResource; }
        void FinishFlushParams() { bShouldFlushParams = false; }
        void FinishFlushResource() { bShouldFlushResource = false; }

        bool HasTexture(uint32_t id) const;
        const TextureView* GetTextureView(uint32_t id) const;
        void SetTextureView(uint32_t id, PiccoloTexture* texture, PiccoloSampler* sampler);
        void UpdateTextureViewEnable(uint32_t id, bool enable);
        void UpdateTextureViewUVTranslation(uint32_t id, const glm::vec2& uvTranslation);
        void UpdateTextureViewUVRotation(uint32_t id, float uvRotation);
        void UpdateTextureViewUVScale(uint32_t id, const glm::vec2& uvScale);

    protected:
        PMaterial() = default;

        bool bShouldFlushParams = false;                // params是否发生变更
        bool bShouldFlushResource = false;              // texture是否发生替换

    private:
        std::unordered_map<uint32_t, TextureView> mTextures;
        int32_t mIndex = -1;
        friend class PMaterialFactory;
    };

    class PMaterialFactory
    {
    public:
        PMaterialFactory(const PMaterialFactory&) = delete;
        PMaterialFactory& operator=(const PMaterialFactory&) = delete;

        ~PMaterialFactory() { mMaterials.clear(); }

        static PMaterialFactory* GetInstance() { return &s_MaterialFactory; }

        template<typename T>
        size_t GetMaterialSize()
        {
            uint32_t typeId = entt::type_id<T>().hash();
            if (mMaterials.find(typeId) == mMaterials.end())
            {
                return 0;
            }
            return mMaterials[typeId].size();
        }

        template<typename T>
        T* CreateMaterial()
        {
            auto material = std::make_shared<T>();
            uint32_t typeId = entt::type_id<T>().hash();
            uint32_t index = 0;
            if (mMaterials.find(typeId) == mMaterials.end())
            {
                mMaterials.insert({ typeId, { material } });
            }
            else
            {
                index = mMaterials[typeId].size();
                mMaterials[typeId].push_back(material);
            }
            material->mIndex = index;
            return material.get();
        }

    private:
        PMaterialFactory() = default;

        static PMaterialFactory s_MaterialFactory;

        std::unordered_map<uint32_t, std::vector<std::shared_ptr<PMaterial>>> mMaterials;
    };
}

#endif